package com.atguigu.gulimall.order.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.OrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberEntity;
import com.atguigu.gulimall.order.constant.OrderConstant;
import com.atguigu.gulimall.order.entity.OrderItemEntity;
import com.atguigu.gulimall.order.entity.OrderReturnReasonEntity;
import com.atguigu.gulimall.order.exception.NoStockException;
import com.atguigu.gulimall.order.feign.CartFeignService;
import com.atguigu.gulimall.order.feign.MemberFeignService;
import com.atguigu.gulimall.order.feign.ProductFeignService;
import com.atguigu.gulimall.order.feign.WmsFeignService;
import com.atguigu.gulimall.order.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.order.service.OrderItemService;
import com.atguigu.gulimall.order.to.OrderCreateTo;
import com.atguigu.gulimall.order.vo.*;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.order.dao.OrderDao;
import com.atguigu.gulimall.order.entity.OrderEntity;
import com.atguigu.gulimall.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@Service("orderService")
@RabbitListener(queues={"hello-java-queue"})
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    private ThreadLocal<OrderSubmitVo> submitVoThreadLocal = new ThreadLocal<>();

    @Autowired
    private ThreadPoolExecutor executor;

    @Autowired
    private MemberFeignService memberFeignService;

    @Autowired
    private CartFeignService cartFeignService;

    @Autowired
    private WmsFeignService wmsFeignService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 关单操作
    @Override
    public void closeOrder(OrderEntity orderEntity) {
        // 1.查询当前订单的最新状态
        OrderEntity entity = this.getById(orderEntity.getId());
        // 如果还是未付款状态,就进行关单操作 CREATE_NEW(0,"待付款")
        if(OrderConstant.OrderStatusEnum.CREATE_NEW.getCode().equals(entity.getStatus())){
            OrderEntity order = new OrderEntity();
            order.setId(orderEntity.getId());
            order.setStatus(OrderConstant.OrderStatusEnum.CANCLED.getCode());
            this.updateById(order);
            // 向MQ发送解锁库存信息
            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(orderEntity,orderTo);
            rabbitTemplate.convertAndSend("order.event.exchange","order.release.other",orderTo);
        }
    }

    // 封装订单实体类对象 订单+订单项
    private OrderCreateTo createOrder(){
        OrderCreateTo createTo = new OrderCreateTo(); // 订单
        // 订单号的创建
        String orderSn = IdWorker.getTimeId();
        // 构建订单信息
        OrderEntity orderEntity = buildOrder(orderSn);
        // 构建订单项信息
        List<OrderItemEntity> orderItems = buildOrderItems(orderSn);
        // 计算价格相关-->设置订单的价格
        computePrice(orderEntity,orderItems);
        createTo.setOrder(orderEntity);
        createTo.setOrderItems(orderItems);
        return createTo;
    }

    private void computePrice(OrderEntity orderEntity,List<OrderItemEntity> orderItems){
        // 设置订单总额,促销,优惠券,积分,成长值
        BigDecimal total = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");
        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal integration = new BigDecimal("0.0");
        BigDecimal gift = new BigDecimal("0.0");
        BigDecimal growth = new BigDecimal("0.0");

        for (OrderItemEntity item : orderItems) {
            promotion = promotion.add(item.getPromotionAmount());
            coupon = coupon.add(item.getCouponAmount());
            integration = integration.add(item.getIntegrationAmount());
            gift = gift.add(new BigDecimal(item.getGiftIntegration()));
            growth = growth.add(new BigDecimal(item.getGiftGrowth()));
            total = total.add(item.getRealAmount());
        }
        // 设置订单总额 这个总额已经把每一项的优惠减去了
        orderEntity.setTotalAmount(total);
        // 设置应付总额，所以就用订单总额+运费
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        // 设置各种信息
        orderEntity.setPromotionAmount(promotion);
        orderEntity.setCouponAmount(coupon);
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setIntegration(integration.intValue());
        orderEntity.setGrowth(growth.intValue());
    }

    // 构建所有订单项数据
    private List<OrderItemEntity> buildOrderItems(String orderSn){
        // 获取到所有的订单项
        List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
        if(currentUserCartItems!=null && currentUserCartItems.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItems.stream().map(cartItem -> {
                OrderItemEntity itemEntity = buildOrderItem(orderSn, cartItem);
                itemEntity.setOrderSn(orderSn);
                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }else{
            return null;
        }
    }

    // 将构建订单项信息的代码抽取成方法
    private OrderItemEntity buildOrderItem(String orderSn,OrderItemVo orderItemVo){
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        // 1.设置订单号信息
        orderItemEntity.setOrderSn(orderSn);
        // 2.设置spu信息
        R r = productFeignService.getSpuInfoBySkuId(orderItemVo.getSkuId());
        SpuInfoVo spuInfoVo = r.getData("data", new TypeReference<SpuInfoVo>(){});
        orderItemEntity.setSpuId(spuInfoVo.getId());
        orderItemEntity.setSpuName(spuInfoVo.getSpuName());
        orderItemEntity.setSpuBrand(spuInfoVo.getBrandId().toString());
        orderItemEntity.setCategoryId(spuInfoVo.getCatalogId());
        // 3. 设置sku信息
        orderItemEntity.setSkuId(orderItemVo.getSkuId());
        orderItemEntity.setSkuName(orderItemVo.getTitle());
        orderItemEntity.setSkuPic(orderItemVo.getDefaultImg());
        orderItemEntity.setSkuPrice(orderItemVo.getPrice());
        orderItemEntity.setSkuQuantity(orderItemVo.getCount());
        /***
         * StringUtils.collectionToDelimitedString 是 Apache Commons Lang 库中提供的一个工具方法，
         * 用于将集合转换为由分隔符分隔的字符串。此方法接受一个集合（如 List、Set 等）作为输入，
         * 遍历集合中的每个元素，并在每个元素之间插入指定的分隔符，最终生成一个字符串输出。
         */
        String attrs = StringUtils.collectionToDelimitedString(orderItemVo.getSkuAttr(), ";");
        orderItemEntity.setSkuAttrsVals(attrs);
        // 4.设置优惠券信息(不做)
        // 5.设置积分信息
        orderItemEntity.setGiftGrowth(orderItemVo.getPrice().
                multiply(new BigDecimal(orderItemVo.getCount().toString())).intValue());
        orderItemEntity.setGiftIntegration(orderItemVo.getPrice().
                multiply(new BigDecimal(orderItemVo.getCount().toString())).intValue());

        // 6.计算单个购物项的价格
        orderItemEntity.setPromotionAmount(new BigDecimal("0"));
        orderItemEntity.setCouponAmount(new BigDecimal("0"));
        orderItemEntity.setIntegrationAmount(new BigDecimal("0"));
        BigDecimal totalPrice = new BigDecimal(orderItemEntity.getSkuPrice().toString()).multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
        BigDecimal realAmount = totalPrice.subtract(orderItemEntity.getPromotionAmount()).subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getIntegrationAmount());
        orderItemEntity.setRealAmount(realAmount);

        return orderItemEntity;
    }

    // 将构造订单数据的代码抽取成方法
    private OrderEntity buildOrder(String orderSn){
        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setOrderSn(orderSn);
        // 调用远程仓储服务获取地址和运费信息
        OrderSubmitVo orderSubmitVo = submitVoThreadLocal.get();
        String addrId = orderSubmitVo.getAddrId();
        R fare = wmsFeignService.getFare(Long.valueOf(addrId));
        AddrAndFareVo addrAndFareVo = fare.getData("data", new TypeReference<AddrAndFareVo>(){});
        // 设置运费信息
        orderEntity.setFreightAmount(new BigDecimal(addrAndFareVo.getFare()));
        // 设置地址信息
        orderEntity.setReceiverName(addrAndFareVo.getMemberReceiveAddressEntity().getName());
        orderEntity.setReceiverPhone(addrAndFareVo.getMemberReceiveAddressEntity().getPhone());
        orderEntity.setReceiverPostCode(addrAndFareVo.getMemberReceiveAddressEntity().getPostCode());
        orderEntity.setReceiverProvince(addrAndFareVo.getMemberReceiveAddressEntity().getProvince());
        orderEntity.setReceiverCity(addrAndFareVo.getMemberReceiveAddressEntity().getCity());
        orderEntity.setReceiverRegion(addrAndFareVo.getMemberReceiveAddressEntity().getRegion());
        orderEntity.setReceiverDetailAddress(addrAndFareVo.getMemberReceiveAddressEntity().getDetailAddress());

        orderEntity.setStatus(OrderConstant.OrderStatusEnum.CREATE_NEW.getCode());
        orderEntity.setDeleteStatus(0);
        orderEntity.setConfirmStatus(0);
        return orderEntity;
    }

    // 提交订单
    // 注意:本地事务的前提是在一个数据库DB里面,使用的是同一条连接,比如我们和数据库建立了一个连接,就在这个连接里面发了三个sql,只要有任何一个sql失败,这三个sql全部回滚
    // 本地事务使用非常简单,只需要Spring的一个@Transactional注解就可以使用.
    // 本地事务在分布式系统,只能控制住自己的回滚(比如自己的service调用,而且还得是同一个数据库),控制不了其他服务(比如远程调用)的回滚
    // 分布式事务: 最大问题:网络问题+分布式机器 无法感知分布式事务是真失败还是假失败
    @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
        responseVo.setCode(0);
        MemberEntity memberEntity = LoginUserInterceptor.loginUser.get();
        // 获取浏览器保存的token
        String token = orderSubmitVo.getOrderToken();
        // 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 1.验证令牌 : 必须保证令牌验证和删除的原子性,所以用了lua脚本
        Long response = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberEntity.getId().toString()),
                token);
        if (response==0){
            // 令牌校验失败
            responseVo.setCode(1);

            return responseVo;
        } else {
            submitVoThreadLocal.set(orderSubmitVo);
            // 令牌校验成功
            // 下单:去创建订单吗,验令牌,验价格,锁库存....
            // 创建订单
            OrderCreateTo createTo = createOrder();
            // 验价:页面提交价格与计算的价格相差小于0.01则验价成功
            BigDecimal payAmount = createTo.getOrder().getPayAmount();
            if(Math.abs(orderSubmitVo.getPayPrice().subtract(payAmount).doubleValue())<0.01){
                // 验价成功,保存订单
                saveOrder(createTo);
                // 锁库存
                OrderLockVo lockVo = new OrderLockVo();
                lockVo.setOrderSn(createTo.getOrder().getOrderSn());
                List<OrderItemVo> orderItemVos = createTo.getOrderItems().stream().map(item -> {
                    OrderItemVo orderItemVo = new OrderItemVo();
                    orderItemVo.setSkuId(item.getSkuId());
                    orderItemVo.setCount(item.getSkuQuantity());
                    orderItemVo.setTitle(item.getSkuName());
                    return orderItemVo;
                }).collect(Collectors.toList());
                lockVo.setLocks(orderItemVos);
                // TODO 远程锁库存
                // 库存成功了,但是网络原因超时了,订单回滚,库存不滚
                R r = wmsFeignService.lockOrderStock(lockVo);
                if(r.getCode()==0){
                    // 锁库存成功
                    responseVo.setOrderEntity(createTo.getOrder());

                    // TODO 给MQ发送关闭订单操作
                    rabbitTemplate.convertAndSend("order.event.exchange","order.create.order",createTo.getOrder());

                    // TODO 假如远程扣减积分出异常
                    // 订单回滚,库存不滚
                    // int i = 10/0
                    return responseVo;
                }else{
                    // 锁库存失败
                    // throw new NoStockException("库存锁定失败,商品库存不足");
                   //responseVo.setCode(3);
                   //return responseVo;
                    String msg = (String)r.get("msg");
                    // 锁库存失败必须抛出异常,只有这样才能使所在方法的事务回滚
                    throw new NoStockException(msg);
                }
            }else{
                responseVo.setCode(2);
                return responseVo;
            }
        }
    }

    private void saveOrder(OrderCreateTo createTo){
        MemberEntity memberRespVo = LoginUserInterceptor.loginUser.get();
        OrderEntity order = createTo.getOrder();
        // 封装order的其他属性
        order.setModifyTime(new Date());
        order.setMemberId(memberRespVo.getId());
        // 保存订单数据
        this.save(order);
        List<OrderItemEntity> orderItems = createTo.getOrderItems();
        // 保存订单项数据
        orderItemService.saveBatch(orderItems);
    }

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        MemberEntity memberEntity = LoginUserInterceptor.loginUser.get();

        // 获取之前的请求
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> getAddressTask1 = CompletableFuture.runAsync(() -> {
            // 每个新线程都共享之前的请求数据
            RequestContextHolder.setRequestAttributes(attributes);
            // 1.查询用户的收货地址
            List<MemberAddressVo> address = memberFeignService.getAddress(memberEntity.getId());
            orderConfirmVo.setMemberAddressVoList(address);
        }, executor);

        CompletableFuture<Void> getCartItemTask2 =  CompletableFuture.runAsync(()->{
            // 每个新线程都共享之前的请求数据
            RequestContextHolder.setRequestAttributes(attributes);
            // 2.查询购物车(Redis)
            List<OrderItemVo> cartItems = cartFeignService.getCurrentUserCartItems();
            orderConfirmVo.setItems(cartItems);
        },executor).thenRunAsync(()->{
            List<OrderItemVo> itemVos = orderConfirmVo.getItems();
            // 收集skuIds
            List<Long> skuIds = itemVos.stream().map(item -> {
                return item.getSkuId();
            }).collect(Collectors.toList());
            // 远程服务查询库存信息
            R r = wmsFeignService.queryStock(skuIds);
            List<SkuStockVo> skuStockVos = r.getData(new TypeReference<List<SkuStockVo>>(){});
            // 将库存信息收集成Map
            Map<Long, Boolean> map = skuStockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
            orderConfirmVo.setHasStock(map);
        },executor);

        // 3.查询积分
        Integer integration = memberEntity.getIntegration();
        orderConfirmVo.setIntegration(integration);
        // 4.等待任务执行完成
        CompletableFuture.allOf(getAddressTask1,getCartItemTask2).get();

        // TODO 5.防重令牌的生成
        // 防重令牌,Redis中存一份,页面存储一份
        String token = UUID.randomUUID().toString().replace("-", "");
        // 将防重令牌存储至Redis
        stringRedisTemplate.opsForValue().set(
                OrderConstant.USER_ORDER_TOKEN_PREFIX+memberEntity.getId().toString(),token,30, TimeUnit.MINUTES);
        // 将防重令牌提交给页面
        orderConfirmVo.setOrderToken(token);
        return orderConfirmVo;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    //@RabbitListener(queues = {"hello-java-queue"})
    public void receiveMessage(Object msg){
        // 接收到消息....内容: (Body:'[B@13c2c494(byte[93])' MessageProperties [headers={__TypeId__=com.atguigu.gulimall.order.entity.OrderReturnReasonEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-directExchange, receivedRoutingKey=helloQueue, deliveryTag=1, consumerTag=amq.ctag-bQpcIAj5KCZhj8pxPgcyig, consumerQueue=hello-java-queue])===>类型:class org.springframework.amqp.core.Message
        System.out.println("接收到消息....内容: "+msg+"===>类型:"+msg.getClass());
    }

    //@RabbitListener(queues = {"hello-java-queue"})
    public void receiveMessage(Message msg){
        // 获取消息体
        byte[] body = msg.getBody();
        // 获取消息头
        MessageProperties header = msg.getMessageProperties();
        // 接收到消息....内容: (Body:'[B@752ec369(byte[93])' MessageProperties [headers={__TypeId__=com.atguigu.gulimall.order.entity.OrderReturnReasonEntity}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=hello-java-directExchange, receivedRoutingKey=helloQueue, deliveryTag=1, consumerTag=amq.ctag-eCdU7p-TWxNCAatWe---5Q, consumerQueue=hello-java-queue])===>类型:class org.springframework.amqp.core.Message
        System.out.println("接收到消息....内容: "+msg+"===>类型:"+msg.getClass());
    }

    //@RabbitListener(queues = {"hello-java-queue"})
    public void receiveMessage(Message msg,OrderReturnReasonEntity content){
        // 获取消息体
        byte[] body = msg.getBody();
        // 获取消息头
        MessageProperties header = msg.getMessageProperties();
        // 接收到消息....内容: OrderReturnReasonEntity(id=1, name=哈哈哈哈哈Jackson, sort=null, status=null, createTime=Wed Apr 17 05:07:08 CST 2024)
        System.out.println("接收到消息....内容: "+content);
    }

    //Channel:当前传输数据的通道
    //@RabbitListener(queues = {"hello-java-queue"})
    /**
    public void receiveMessage(Message msg, OrderReturnReasonEntity content, Channel channel){
        // 获取消息体
        byte[] body = msg.getBody();
        // 获取消息头
        MessageProperties header = msg.getMessageProperties();
        // 接收到消息....内容: OrderReturnReasonEntity(id=1, name=哈哈哈哈哈Jackson, sort=null, status=null, createTime=Wed Apr 17 05:07:08 CST 2024)
        System.out.println("接收到消息....内容: "+content);
    }*/

    //@RabbitListener(queues = {"hello-java-queue"})
    public void receiveMessage(Message msg, OrderReturnReasonEntity content, Channel channel){
        // 获取消息体
        byte[] body = msg.getBody();
        // 获取消息头
        MessageProperties header = msg.getMessageProperties();
        // 接收到消息....内容: OrderReturnReasonEntity(id=1, name=哈哈哈哈哈Jackson, sort=null, status=null, createTime=Wed Apr 17 05:07:08 CST 2024)
        System.out.println("接收到消息....内容: "+content.getName());
    }

    /***
     * 总结:
     * 如何签收：
     * 业务成功就应该签收：channel.basicAck(deliveryTag,false);
     * 业务处理失败就应该拒签，让别人处理：channel.basicNack(deliveryTag,false,true);
     * @param msg
     * @param orderReturnReasonEntity
     * @param channel
     * @throws IOException
     */
    @RabbitHandler
    public void receiveMessage01(Message msg,OrderReturnReasonEntity orderReturnReasonEntity,Channel channel) throws IOException {
        // 获取deLiverTag
        long deliveryTag = msg.getMessageProperties().getDeliveryTag();
        System.out.println("接收到的消息...内容:"+orderReturnReasonEntity.getName());
        try{
            // 手动ACK deliverTag:相当于消息标识
            // mutiple:是否批处理,如果是true的话,如果有一个消息没有收到,其他消息都会重新获取(即使是已经接收到的消息)
            // requeue: 是否重新入队
            channel.basicAck(deliveryTag,false);
        }catch (Exception e){
            // 网络异常
            // 手动ACK deliverTag:相当于消息标识
            // mutiple:是否批处理,如果是true的话,如果有一个消息没有收到,其他消息都会重新获取(即使是已经接收到的消息)
            // requeue: 是否重新入队
            channel.basicNack(deliveryTag,false,true);
        }

    }

    @RabbitHandler
    public void receiveMessage02(OrderEntity orderEntity){
        System.out.println("接收到的消息...内容:"+orderEntity.getOrderSn());
    }
}